Quest 8: Project - Group Buying Web App

Availability
Ended
Quest master
StackUp
Est. time
1 hour(s)
Your status
Rewarded
Reward
$15
No. of rewards
400
Start date
17 Feb 2023, 9:30 am
Deadline
28 Feb 2023, 9:30 am
Review by
10 Mar 2023, 9:30 am
Created
04 Feb 2023, 1:47 pm
Introduction
In this quest, we will be writing two smart contracts that allow multiple buyers to place orders in USDC tokens. We will also be creating a group buying web application which provides us with an interface to interact with the smart contracts.
To successfully test out the web application, you will need to have three accounts in your MetaMask wallet. Each account should have at least 0.1 GoerliETH and 10 USDC tokens.
  • For the USDC tokens, you can head to this faucet and obtain them over the Ethereum Goerli network. To ensure the tokens show up in your MetaMask accounts, you should import the token.
  • For the GoerliETH tokens, you can obtain them from several faucets like:

For queries on this quest,  join our Discord, head to the #circle-helpdesk channel and look for this quest's post.

In the event that you are not able to view certain quest steps, you may head here.
Learning Outcomes
By the end of this quest, you will be able to: 
  • Use more advanced smart contract concepts such as interfaces and mapping
  • Deploy a multiple file smart contract on Remix IDE
  • Build and run a group buying web application

Deliverable
To successfully complete this quest, you will need to submit the following:
  1. Screenshot of the deployed smart contract on Etherscan. See Step 4 for more details.
  2. Screenshot of two ‘Place order’ and one ‘Withdraw funds’ activities in the group buy smart contract. See Step 11 for more details.

In the event that you are not able to view certain quest steps, you may head here.

Questing solo?

Discuss about this quest with your fellow Stackies on Discord! They're a friendly bunch and always down to lend a hand.
Quest Walkthrough
Step 1: Introduction to Group Buy
A group buy is a community purchase of items that range from groceries to furniture. The main benefit of these group buys are that the buyers can get these items below retail price due to the bulk order. Usually in group buys there is a host that will collate and place the orders with the supplier. They will also be responsible for shipping out the items to the buyers when they receive the items from the supplier. One example of a group buy application is WeBuy.
Step 2: Understanding the Group Buy Smart Contract
For this project, we will be using two smart contracts – one for group buys and another as a manager for the group buys. We will explain the use case of each smart contract individually. In this step, we focus on the functions and variables of the group buy smart contract.
You can view how the group buy smart contract looks like in 'working code' at the end of this step.

The group buy smart contract controls everything related to the individual group buy such as the placing of orders and the withdrawal of funds. For this smart contract, we are expecting it to do the following:
  • Be able to store information regarding the group buy such as the end time, price, product name, and buyers.
  • The buyers are able to place orders using the smart contract provided they did not place one earlier.
  • The seller is able to withdraw funds after the group is closed.
  • The seller can only close the group buy after the end time has reached.

For the smart contract, we will be using the following variables.



An array is a collection of the same data type values stored at adjacent memory locations. To visualize how an array works, imagine that there is a staircase with each step containing a value. Let’s say you want to get the value of the 7th step. You will need to find the first step and count to the 7th step before you can access the value of the 7th step. Basically, it calculates the 7th element's address using the 1st element’s address as a reference.



An interface details the collection of functions supported by a data structure. In this case, it details an object and the semantics of those functions. The interface does not tell us how the functions are executed. Instead, it shows the required inputs and the expected output after the function is executed. The USDC interface below shows the functions that the USDC token smart contract contains. You may find this familiar because we used this interface in the previous quest. 



Now that you know what variables will be used, we will be going through the functions in the smart contract. In this smart contract, there are 6 functions. The first function is the constructor which will be called when a new group is created on the frontend with the required input. This constructor function will add values to the variables that we mentioned earlier. In line 49, we are connecting the interface to the USDC token smart contract and by doing so we can utilize the USDC token smart contract functions such as balanceOf and transfer.



The getGroupBuyState function gets the current state of the group buy. It determines whether a group buy is open or closed by checking if the end time has passed. For this group buy, the seller cannot prematurely close a group buy so the only determining factor is the end time. If the end time has not passed, then it will still be considered open.



The hasCurrentBid function will check if the buyer has a current order. This is to prevent duplicate orders for the group buy. The function traverses the array of the buyers’ wallet addresses and determines if any of them matches the address that was passed into the function. If there is a match, it will return true. Otherwise, it will return false.



The function that we will be looking at next is the getAllOrders function where it will retrieve all the buyers’ wallet addresses. It will return all the buyer’s wallet addresses for that group buy.



The next function is the placeOrder function which will be called when an order is placed. When this function is called, it must pass 3 checks. The first is that the buyer wallet address calling the function must not be the same as the seller. This is to prevent the seller from placing orders and inflating order numbers. The second is that the group buy must be open and it should not accept orders when it is closed. The third is that the user placing the order must not already have an order in place to prevent duplicate orders. Once these 3 checks have passed, the function will then proceed to transfer the USDC from the buyer wallet address and transfer it to the group buy smart contract. Once the transfer is successful, it will then add the buyer’s wallet address to the array of buyers’ wallet addresses and emit the NewOrder event.

An event is used to record actions that occur in a smart contract. These events are stored and can be viewed on the blockchain. For our smart contract, we have 3 events and they are NewOrder, WithdrawFunds and GroupBuyClosed. These events are found in lines 40 to 42.



Finally, let's look at the withdrawFunds function. This function is called when the group buy has ended and the seller wants to withdraw the funds. The function will first check if the group buy has indeed ended and if the wallet address of the request sender matches the seller’s wallet address. If both checks pass, then it will transfer the USDC to the seller and emit the events WithdrawFunds and GroupBuyClosed which will record the end of the group buy.
Working code
// SPDX-License-Identifier: Unlicense pragma solidity ^0.8.17;   // Abstract interface USDC {   function balanceOf(address account) external view returns (uint256);     function allowance(address owner, address spender)     external     view     returns (uint256);     function transfer(address recipient, uint256 amount)     external     returns (bool);     function approve(address spender, uint256 amount) external returns (bool);     function transferFrom(     address sender,     address recipient,     uint256 amount   ) external returns (bool); } contract GroupBuy {   uint256 public endTime;   uint256 public startTime;   address payable[] public buyers;    uint256 public price;    address public seller;    string public productName;    string public productDescription;    USDC public USDc;     enum GroupBuyState {     OPEN,     ENDED   }     event NewOrder(address newBuyer);   event WithdrawFunds();   event GroupBuyClosed();     constructor(     address _seller,     uint256 _endTime,     uint256 _price,     string memory _productName,     string memory _productDescription   ) {     USDc = USDC(0x07865c6E87B9F70255377e024ace6630C1Eaa37F);      seller = _seller;      endTime = block.timestamp + _endTime;     startTime = block.timestamp;       price = _price;       productName = _productName;     productDescription = _productDescription;   }     function placeOrder() external payable returns (bool) {     require(msg.sender != seller);     require(getGroupBuyState() == GroupBuyState.OPEN);       require(hasCurrentBid(msg.sender) == false);     USDc.transferFrom(msg.sender, address(this), price);     buyers.push(payable(msg.sender));     emit NewOrder(msg.sender);     return true;   }   function withdrawFunds() external returns (bool) {     require(getGroupBuyState() == GroupBuyState.ENDED);       require(msg.sender == seller);      USDc.transfer(seller, price * buyers.length);       emit WithdrawFunds();       emit GroupBuyClosed();     return true;   }   function getGroupBuyState() public view returns (GroupBuyState) {     if (block.timestamp >= endTime) return GroupBuyState.ENDED;     return GroupBuyState.OPEN;     }   function hasCurrentBid(address buyer) public view returns (bool) {     bool isBuyer = false;     for (uint256 i = 0; i < buyers.length; i++) {       if (buyers[i] == buyer) {         isBuyer = true;       }     }     return isBuyer;   }      function getAllOrders()     external     view     returns (address payable[] memory _buyers)   {     return buyers;   } }
Step 3: Group Buy Manager Smart Contract
For this step, we will look at the group buy manager smart contract to see how it works.
To see how the group buy manager smart contract looks like, check out the 'working code' at the end of this step.

For the group buy manager, we are expecting it to do the following:
  • Create group buys.
  • Retrieve the list of group buys smart contract addresses.
  • Retrieve information about a group buy based on a list of group smart contract addresses.

Let's take a look at the variables that we will be using for this smart contract.



A map is a collection of key-value pairs like an Object or a Dictionary data structure. To access a value, you will need to provide the key or identifier and it will return the value. The difference between a map and an array is how the data is stored. As we learned earlier, the array stores its data in sequential memory locations. Whereas for a map, the values are not necessarily stored sequentially, but instead the locations are recorded on the mapping and can be accessed with the key value. 

In terms of accessing an element, both mapping and array achieve this in what is known as “constant time” in algorithms, meaning the time taken is not dependent on factors such as the size of the array or map. One advantage of mapping is that the key value is not restricted to only numbers like on the array. You can have string keys as well. Both map and array are used in this smart contract to show you how they can be used differently.

In this next part, we will go through the functions that are available for this group buy smart contract.



The first function is createGroupBuy and as the name suggests it is called by the user when creating a group buy. This function will take in 4 inputs which are necessary to set up the group buy smart contract. There are 2 checks that need to be done before a group buy smart contract is created. The first is that the price is more than 0. For prices, we will not accept zero or negative values as prices are usually not within that range. The second check is that the group buy end time is longer than 5 minutes from the current time. This would ensure that there is enough time for the group buy order to be placed. Once the checks are done, it will get the new group buy ID from the counter and then it will increment that counter to prevent repeated values. After that, the group buy smart contract will be created and it will be added to the group buy array and it will map the address of the new group buy smart contract to the allocated ID. 



The second function is getGroupBuys and this returns a list of group buys that the group buy manager is managing. This includes all group buys including those that have already ended. To do this, it will iterate through the groupBuys array using a for loop. For each iteration, it will get the address of the group buy contract and add it to the list of addresses. At the end of the loop, it will return the accumulated list of addresses. 



The next function that we will be looking at is the getGroupBuyInfo. This function takes in a list of group buy smart contract addresses and returns information of a list of group buys. Information provided will include name, description, price, seller address, the end time and the current state of the group buy. The first thing that this function does is declare several arrays with the length fixed at the size of the list. These arrays will store the various information that we stated above. 

Next, the function will iterate through the array of the input addresses and for each iteration, it will fetch the group buy ID for the corresponding smart contract address. By mapping the contract address to the group buy ID instead of an array, we can access the group buy ID in a shorter amount of time. This is because when finding a specific value on an array without knowing its position, you will need to iterate through the entire array and check for values that match the desired value. This means that the time taken to get the position of the value is proportionate to its distance from the first value. Once we have the group buy ID, we can access the group buy smart contract reference object. 

After the iterations are done, the various arrays are returned by the function to the caller.
Working code
Step 4: Deploy Smart Contract to Goerli Test Network
In this step we will be writing a smart contract and deploying it to the Goerli test network. Like the previous quest, we will need to access the Remix Online IDE website.
First, head to Remix IDE and create a new file named groupBuy.sol in the default workspace's root directory. 

In groupBuy.sol, copy and paste the code found in 'working code' which is at the end of this step. This is the group buy smart contract that we covered in Step 2. Then, save the file by pressing Ctrl / Cmd (depending on your OS) + S.  

Next, create a new file named groupBuyManager.sol in the same directory. In the file, copy and paste the code found in 'working code' which is at the end of this step. This is the group buy manager smart contract which we covered in Step 3. Then, save the file and compile the smart contract. 

Disclaimer: If you decide to make any changes to the smart contract like adding in functions or variables, you will need to update your project ABI in the next step. It is recommended to follow the code provided.

Next, head to the “Deploy and Run Transactions” section, which is accessed by clicking the fifth icon on the left sidebar. In this section, click on the environment dropdown box and select “Injected Provider- MetaMask”. If your wallet is not connected, a pop up will appear asking you to connect your wallet to the IDE. 

Next, select the contract that you want to deploy. In the “Contract” dropdown box, select “GroupBuyManager - groupBuyManager.sol”. You must be wondering, why are we not deploying the group buy smart contract? In the group buy manager code, we imported the code from the group buy smart contract in line 4. One thing to note is that as long as you import the smart contract to the manager smart contract you are deploying, you will be able to deploy the imported smart contract from the manager smart contract, which is what we are doing here. Another point to note is that the group buy smart contract will only be created and deployed when a new group is created on the frontend or the createGroupBuy function in the group buy manager smart contract is called.



Hit the deploy button and you should see an output in the terminal, similar to the image below. Click on “view on etherscan” and to view the transaction.



On Etherscan, you will see the smart contract deployment transaction details. In the 'To:' field, click on the contract address. 

On the contract page, you will see information about the smart contract that you have deployed such as the transactions and events. 



As part of this quest's submission, screenshot this entire page with the “Contract Creation” transaction visible. You can see the 'expected output' to have an idea of how your screenshot should look like. Be sure to copy the smart contract address as that will be used in the later steps.
Working code
//----Code for groupBuy.sol---- // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.17;   // Abstract interface USDC {   function balanceOf(address account) external view returns (uint256);     function allowance(address owner, address spender)     external     view     returns (uint256);     function transfer(address recipient, uint256 amount)     external     returns (bool);     function approve(address spender, uint256 amount) external returns (bool);     function transferFrom(     address sender,     address recipient,     uint256 amount   ) external returns (bool); } contract GroupBuy {   uint256 public endTime;   uint256 public startTime;   address payable[] public buyers;    uint256 public price;    address public seller;    string public productName;    string public productDescription;    USDC public USDc;     enum GroupBuyState {     OPEN,     ENDED   }     event NewOrder(address newBuyer);   event WithdrawFunds();   event GroupBuyClosed();     constructor(     address _seller,     uint256 _endTime,     uint256 _price,     string memory _productName,     string memory _productDescription   ) {     USDc = USDC(0x07865c6E87B9F70255377e024ace6630C1Eaa37F);      seller = _seller;      endTime = block.timestamp + _endTime;     startTime = block.timestamp;       price = _price;       productName = _productName;     productDescription = _productDescription;   }     function placeOrder() external payable returns (bool) {     require(msg.sender != seller);     require(getGroupBuyState() == GroupBuyState.OPEN);       require(hasCurrentBid(msg.sender) == false);     USDc.transferFrom(msg.sender, address(this), price);     buyers.push(payable(msg.sender));     emit NewOrder(msg.sender);     return true;   }   function withdrawFunds() external returns (bool) {     require(getGroupBuyState() == GroupBuyState.ENDED);       require(msg.sender == seller);      USDc.transfer(seller, price * buyers.length);       emit WithdrawFunds();       emit GroupBuyClosed();     return true;   }   function getGroupBuyState() public view returns (GroupBuyState) {     if (block.timestamp >= endTime) return GroupBuyState.ENDED;     return GroupBuyState.OPEN;     }   function hasCurrentBid(address buyer) public view returns (bool) {     bool isBuyer = false;     for (uint256 i = 0; i < buyers.length; i++) {       if (buyers[i] == buyer) {         isBuyer = true;       }     }     return isBuyer;   }      function getAllOrders()     external     view     returns (address payable[] memory _buyers)   {     return buyers;   } }


//----Code for groupBuyManager.sol----
// SPDX-License-Identifier: Unlicense pragma solidity ^0.8.17;   import "./groupBuy.sol";   contract GroupBuyManager {   uint256 _groupBuyIDCounter;   GroupBuy[] public groupBuys;   mapping(address => uint256) public groupBuysIDs;   function createGroupbuy(     uint256 _endTime,     uint256 _price,     string calldata _productName,     string calldata _productDescription   ) external returns (bool) {     require(_price > 0);     require(_endTime > 5 minutes);       uint256 groupBuyID = _groupBuyIDCounter;     _groupBuyIDCounter++;     GroupBuy groupBuy = new GroupBuy(       msg.sender,       _endTime,       _price,       _productName,       _productDescription     );     groupBuys.push(groupBuy);     groupBuysIDs[address(groupBuy)] = groupBuyID;     return true;   }     function getGroupBuys()     external     view     returns (address[] memory _groupBuys)   {     _groupBuys = new address[](_groupBuyIDCounter);     for (uint256 i = 0; i < _groupBuyIDCounter; i++) {       _groupBuys[i] = address(groupBuys[i]);     }     return _groupBuys;   }   function getGroupBuyInfo(address[] calldata _groupBuyList) external view     returns (       string[] memory productName,       string[] memory productDescription,       uint256[] memory price,       address[] memory seller,       uint256[] memory endTime,       uint256[] memory groupBuyState     )   {     endTime = new uint256[](_groupBuyList.length);     price = new uint256[](_groupBuyList.length);     seller = new address[](_groupBuyList.length);     productName = new string[](_groupBuyList.length);     productDescription = new string[](_groupBuyList.length);     groupBuyState = new uint256[](_groupBuyList.length);       for (uint256 i = 0; i < _groupBuyList.length; i++) {       uint256 groupBuyID = groupBuysIDs[_groupBuyList[i]];       productName[i] = groupBuys[groupBuyID].productName();       productDescription[i] = groupBuys[groupBuyID].productDescription();       price[i] = groupBuys[groupBuyID].price();       seller[i] = groupBuys[groupBuyID].seller();       endTime[i] = groupBuys[groupBuyID].endTime();       groupBuyState[i] = uint256(         groupBuys[groupBuyID].getGroupBuyState()       );     }       return (       productName,       productDescription,       price,       seller,       endTime,       groupBuyState     );   } }
Expected output
Step 5: Writing the Group Buy Web Application
Project Setup We will be cloning a GitHub repository containing files needed to run the group buying web application.

Firstly, open up a terminal and clone the repository into the directory of your choice.
git clone -b circle-quest-8 https://github.com/stackup-dev/circle-campaign.git circle-quest-8

Run the following command to access the directory where the repository is cloned.
cd circle-quest-8

To install the required packages, run the following command.
npm install

Update Project Smart Contract Address
Next, go to the pages directory and open up the index.tsx file. We have declared the ABIs of 3 contracts which are the group buy manager, group buy and the original USDC contract. There is also a custom type ‘GroupBuys’ declared which will be used to store all the data for each group buy.



In line 28, enter the contract address of the group buy manager smart contract you deployed in the previous step.

Let's look at some of the use state variables that are declared below. We have a variable currentWalletAddress to hold and store the user-connected MetaMask wallet address. All the group buy data will be stored in the allGroupBuys array variable. Then we also have an object createGroupBuyFields to store the user inputs when creating a group buy. The activeGroupBuy variable stores the current group buy that the user clicks into to see the details. Lastly, we have the isLoading and loadedData variables to display the loading dialog and dialog text when a process is ongoing.

Step 6: The getAllGroupBuys Function
Let’s move on to the main functions of the group buy application. We will be filling in some code along the way to complete the application in steps. Firstly, let's start off with the getAllGroupBuys function found in line 58 which will retrieve all group buys data from the blockchain. The first part of the function attempts to connect the user's MetaMask wallet. It is the same as Quest 7's connect wallet function and stores the user's wallet address in a variable. 


We will be filling the next part of the code starting from line 81. In order to call functions in the smart contract, we will need to create a contract instance of the group buy manager smart contract using the ethers.Contract library function. The 3 parameters that are passed in as arguments are the group buy manager contract address, the group buy manager ABI as well as the current signer. Copy the code below into your own code editor starting from line 81.
   // 1) add code here    const groupBuyContractManager = new ethers.Contract(     groupBuyManagerContract,     GroupBuyManagerABI,     signer    );

Once we have the contract instance, we can call the getGroupBuys function from the group buy manager smart contract.



The function does not need to take in any parameters and returns an array of addresses of all the current group buys created through this group buy manager contract. Copy the code below into your own code editor starting at line 89.
   // 2) add code here    const groupBuysAddresses = await groupBuyContractManager.getGroupBuys();

Now that we have the list of the current group buys, we will need to call the getGroupBuyInfo function from the group buy manager smart contract to retrieve all the information of each group buy. The getGroupBuyInfo function takes in a list of addresses we will pass in the group buy addresses we obtained earlier into the function. It will then return an array of the group buy data such as productName, productDescription, endTime, and price.



Copy the code below and paste it into your code editor starting from line 93.
   // 3) add code here    const groupBuys = await groupBuyContractManager.getGroupBuyInfo(     groupBuysAddresses    );

Next, we have a for loop that iterates through the group buy info array we got earlier and pushes it into a new array variable new_groupBuys. The array is formatted to have the same typing as the custom type we declared at the start of the program. Copy the code below into your own code editor starting from line 102.
   // 4) add code here    for (let i = 0; i < groupBuys.endTime.length; i++) {     let endTime: number = groupBuys.endTime[i].toNumber();     let groupBuyState: number = groupBuys.groupBuyState[i].toNumber();       let price = groupBuys.price[i]; //     let productName: string = groupBuys.productName[i];     let productDescription: string = groupBuys.productDescription[i];       let sellerAddress: string = groupBuys.seller[i];       let newGroupBuy = {      endTime: endTime,      price: (price / 1000000).toString(),      seller: sellerAddress.toLowerCase(),      groupBuyState: groupBuyState,      productName: productName,      productDescription: productDescription,      groupBuyAddress: groupBuysAddresses[i],      buyers: [],     };     new_groupBuys.push(newGroupBuy);    }

Lastly, in this function we need to set the new_groupBuys variable into the React state variable so that we can display the information needed in the web application later. Copy the code below into your own code editor starting from line 128.
   // 5) add code here    setAllGroupBuys(new_groupBuys);

Your completed getAllGroupBuys function will look similar to what you see in 'working code'. 
Working code
Step 7: The createGroupBuy Function
From line 131 onwards, we will look at the createGroupBuy function. The first portion of the code from line 131 to 150 checks for user input requirements to ensure that they are not empty, the price cannot be below zero and the duration of the group buy is at least 5 minutes.


The first part of the code to be filled in will be calling the createGroupBuy function from the smart contract. The function has 4 parameters – endTime, price, productName and productDescription. The price that we are passing in the function needs to be converted to meet the same format as the USDC token which has 6 decimals places, thus the ethers.utils.parseUnits functionality comes in handy. The gas limit in the code is manually added as sometimes the blockchain network cannot estimate the gas limit for more complex transactions and might return an error. In most cases, we can do without the gas limit in the code. Copy the code below into your own code editor starting from line 172.
    // 1) add code here     let { hash } = await groupBuyContractManager.createGroupbuy(      createGroupBuyFields.endTime * 60, // Converting minutes to seconds      ethers.utils.parseUnits(createGroupBuyFields.price.toString(), 6),       createGroupBuyFields.productName,      createGroupBuyFields.productDescription,      {       gasLimit: 1200000,      }     );

Next, we need to wait for the transaction to be finished before proceeding by using the waitForTransaction functionality. Copy the code below into your own code editor starting from line 184.
    // 2) add code here     await provider.waitForTransaction(hash);

We will display an alert message with the transaction hash to tell the user the transaction has been fully completed. Copy the code below into your own code editor starting from line 191.
    // 3) add code here     alert(`Transaction sent! Hash: ${hash}`);

Your completed createGroupBuy function should look similar to what you see in 'working code'.
Working code
Step 8: The setActiveGroupBuy Function
The setActiveGroupBuy function is triggered when the user clicks on a specific group buy to view the details of it. This function is already completed and there is no need to fill in any additional code. 
The main purpose of this function is to call the group buy smart contract that the user clicks on. It will retrieve the list of all orders placed by any users and set the data into the React state variable to display the information to the user.

Step 9: The placeOrder Function
Now let’s move on to the placeOrder function starting from line 237. For a user to place an order with their USDC tokens, the user has to first give approval to the group buy contract to access the USDC tokens before the user can transfer the USDC tokens over. We will call the approve function in the group buy smart contract that takes in address and amount to be approved. 


We will then pass in the current group buy contract address as the address parameter and for the amount we set it to 1000 USDC. The amount can be changed according to your preference. Copy the code below into your own code editor starting from line 262.
    // 1) add code here     const usdcApprovalTxn = await usdcContract.approve(      currentActiveGroupBuy.groupBuyAddress,      ethers.utils.parseUnits("1000", 6)     );

Next, we will wait for the transaction to be finished. Copy the code below into your own code editor starting from line 269. 
    // 2) add code here     await usdcApprovalTxn.wait();

Now that the approval of the USDC token has been processed, we will use the placeOrder function in the group buy smart contract. The placeOrder function in the smart contract does not take in any parameters. However, we will manually add in the gaslimit in order to avoid getting a gas limit error during the transaction process. Copy the code below into your own code editor starting from line 286.
    // 3) add code here     let { hash } = await groupBuyContract.placeOrder({      gasLimit: 700000,     });

As usual, we will need to wait for the transaction to be completed. Copy the code below into your own code editor starting from line 292.
    // 4) add code here     await provider.waitForTransaction(hash);

Lastly, we display the transaction hash to inform the user the whole process has been completed. Copy the code below into your code editor starting from line 298.
    // 5) add code here     alert(`Transaction sent! Hash: ${hash}`);

Your full completed placeOrder function should look like what you see in 'working code'.
Working code
Step 10: The withdrawFunds Function
Lastly, we have the withdrawFunds function at line 317. The function can only be triggered by the seller and only when the group buy duration has ended. We will be filling in some code to complete this function which is similar to the ones earlier.
As the withdraw function does not take in any parameter we can simply call it. Copy the code below into your code editor starting from line 342.
    // 1) add code here     let { hash } = await groupBuyContract.withdrawFunds();

Next, we wait for the transaction to be completed. Copy the code below into your own code editor starting from line 346.
    // 2) add code here     await provider.waitForTransaction(hash);

Lastly, we display the alert message to notify the user that the process is completed. Copy the code above into your code editor starting from line 352. 
    // 3) add code here     alert(`Transaction sent! Hash: ${hash}`);

Your completed withdrawFunds function code should look like what you see in 'working code'.

Congratulations on sticking with us this far! We have successfully completed filling in the code for the web application.
Working code
Step 11: Running the Application
In this group buying web application demo, we will need to have at least 3 MetaMask accounts. One of them will be the group buy seller and the other two accounts will place an order as buyers since the group buy seller cannot place an order. 
Follow the steps below to create a new account in the same browser.



  • Ensure all three accounts have at least 0.1 GoerliETH and 10 USDC tokens. Note: 0.1 GoerliETH is an estimate to account for gas fees you might incur. At times when the network is heavily in use, gas fees can fluctuate and reach as high as 0.8 GoerliETH per transaction. 
    • You can head to the GoerliETH and USDC faucets like you did in the previous quests. 
    • If your first account has sufficient tokens, you can send some from one account to the other accounts.
    • In the course of completing this step, you realise that the gas fees are too high for you, it is recommended that you try again at a later time when the gas fees have dropped.
  • Remember to import the USDC token, just like you did in Quest 7, so that you can see the balance of the USDC tokens.

Open up a terminal and ensure you are in the same directory as the project. Then, run the following code to start the application in your localhost.
npm run dev

The following output will be shown in your terminal.



Now enter http://localhost:3000 in your browser. The application will be running. A MetaMask pop up will appear asking you to connect your wallet to the site. Click on one of the MetaMask accounts.

Once your wallet is connected, you will be able to see your wallet address at the top right hand corner of your screen.

Now, let's create a group buy. Fill in the fields required then click on the 'Create Group Buy' button. For the duration, set it to between 10 to 20 minutes so that we do not need to wait for long before the group buy ends. Note: Before you set the time, it would be good for you to read the rest of this step to have an idea of what you need to do, and for you to estimate how long you would need.



A MetaMask prompt will pop up. Click on confirm and wait for the transaction to be completed. The following alert message will pop up showing you the transaction hash and that the group buy has been created. Click on the 'OK' button to close the prompt.



The group buy that you just created will show up on your screen. 



Now, switch to your second MetaMask account so that we can use that as a buyer to place an order. Follow the instructions below to switch to your MetaMask account 2.



After your account is disconnected, refresh the page and the MetaMask prompt will pop again. Uncheck account 1, and check account 2. Once the wallet has been connected, check that your wallet address is correctly reflected on the top right hand corner of the application.



On the group buy, click on the ‘See more’ button. You now have the option to place an order using the 'Place Order' button. 



In this place order function, there will be two MetaMask pop-ups – the first one for giving permission to the group buy contract to access the USDC tokens in your current wallet, and the second one to place an order. In both cases, click confirm and wait for the transactions to be completed. There will be a message prompt which says something like "Transaction sent! Hash:". The transaction hash is provided which you can use to verify on the blockchain explorer.

In your application, you will now see your address under the list of buyers. Proceed to place another order using your third MetaMask account. There should be two buyers' wallet addresses reflected in the application.

We will now withdraw funds from the group buy smart contract. Firstly, we will need to switch back to our seller account as only the seller can withdraw funds. Follow the same steps as before to disconnect your current MetaMask account and connect the first MetaMask account to the site.

Ensure the wallet address displayed at the top right hand corner belongs to your seller account. Now that you have account 1 connected, check that the group buy has ended. If it is still ongoing, wait for it to end and refresh the page a few times to ensure the group buy state is updated. 



Now click on the 'See More' button. Other than the two buyers' wallet addresses, you should now see the 'Withdraw Funds' button as shown in the image below. Click on it. A MetaMask pop-up will appear with the withdraw funds function. Click confirm. Wait for the transaction to be complete. On completion, an alert dialog will appear with the confirmed transaction hash. Click on 'OK' to close it.



Now to verify the transactions. On the same page, click on the smart contract address. You will be redirected to the group buy smart contract on the block explorer. On this page in the transactions page, you will see 2 place order events and 1 withdraw funds event that have been triggered by the respective addresses.



At this point, please take a full screenshot of the contract page. Your screenshot should look like the 'expected output'. You will need this for your submission.
Expected output
Step 12: Let's Ace Your Submissions! Preparing Your Submission!
You have reached the end! Now to ensure you successfully complete this quest! There are 2 screenshots that you need to submit.

In Step 4, you had to take a screenshot of your entire screen showing a successful contract creation on Etherscan. 


Your screenshot should show:
  • your full screen, including your taskbar (for Windows and Linux) / dock (for MacOS)
  • The contract address
  • Contract creation transaction
  • All parts in red boxes in Step 4's ‘Expected Output’ are visible in your screenshot!


When labelling your screenshot, make sure to follow the format provided: C58_Q8_yourstackupname_1.png.


In Step 11, you had to take a screenshot of your entire screen showing the placing of orders and withdrawal of funds on Etherscan. 


Your screenshot should show:
  • your full screen, including your taskbar (for Windows and Linux) / dock (for MacOS)
  • At least two 'place order' transactions
  • One 'withdraw funds' transaction
  • The addresses of the seller withdrawing funds and the creator of the contract match
  • All parts in red boxes in Step 11's ‘Expected Output’ are visible in your screenshot!
 
When labelling your screenshot, make sure to follow the format provided: C58_Q8_yourstackupname_2.png.
Back to Campaign
Enabling Payments with Circle
Quiz